home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / python / template_compiler.py < prev    next >
Encoding:
Python Source  |  2007-11-12  |  36.8 KB  |  933 lines

  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005-2007 Participatory Culture Foundation
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  17.  
  18. #
  19. # "Compiles" Democracy templates to Python code
  20. #
  21.  
  22. from util import checkU
  23.  
  24. ###############################################################################
  25. #### Functions used in repeating templates                                 ####
  26. ###############################################################################
  27.  
  28. # These are functions that take in a file object name that will be
  29. # written to in the output file, an optional id, prefix text, and an
  30. # argument
  31.  
  32. # Simply returns text
  33. def genRepeatText(varname, tid, prefix, text):
  34.     checkU(text)
  35.     return u'%s%s.write(%s)\n' % (prefix,varname, repr(text))
  36.  
  37. # Returns translated version of text
  38. def genRepeatTranslate(varname, tid, prefix, args):
  39.     (text, funcDict) = args
  40.     checkU(text)
  41.     # Convert to ascii, strip leading and trailing whitespace and
  42.     # convert interior whitespace to spaces
  43.     text = u' '.join(str(text).strip().split())
  44.  
  45.     if len(funcDict) == 0:
  46.         return u'%s%s.write(_(%s))\n' % (prefix, varname, repr(text))
  47.     else:
  48.         dictName = generateId()
  49.         out = u'%s%s = {}\n' % (prefix, dictName)
  50.         for name in funcDict:
  51.             temp = generateId()
  52.             out = u'%s%s%s = StringIO()\n' % (out, prefix, temp)
  53.             for (func, fargs) in funcDict[name]:
  54.                 val = func(temp, tid, prefix, fargs)
  55.                 checkU(val)
  56.                 out = u'%s%s' % (out, val)
  57.             out = u'%s%s%s.seek(0)\n' % (out, prefix, temp)
  58.             out = u'%s%s%s[%s] = %s.read()\n' % (out, prefix, dictName, repr(str(name)), temp)
  59.  
  60.         out = u'%s%s%s.write(Template(_(%s)).substitute(%s))\n' % (
  61.             out, prefix, varname, repr(text), dictName)
  62.         return out
  63. # Returns text if function does not evaluate to true
  64. def genRepeatTextHide(varname, tid, prefix, args):
  65.     (ifValue, text) = args
  66.     checkU(text)
  67.     out = u'%sif not (%s):\n'%(prefix, ifValue)
  68.     out = u'%s%s    %s.write(%s)\n' % (out, prefix, varname, repr(text))
  69.     return out
  70.         
  71. def genQuoteAttr(varname, tid, prefix, value):
  72.     return u'%s%s.write(quoteattr(urlencode(%s)))\n'%(
  73.         prefix, varname, value)
  74.  
  75. def genRawAttr(varname, tid, prefix, value):
  76.     return u'%s%s.write(quoteattr(%s))\n'%(prefix, varname, value)
  77.  
  78. # Adds a tid attribute to a tag and closes it
  79. def genRepeatTID(varname, tid, prefix, args):
  80.     return u'%s%s.write(quoteattr(tid))\n' % (prefix, varname)
  81.  
  82. # Evaluates key with data
  83. def genRepeatEvalEscape(varname, tid, prefix, replace):
  84.     return u'%s%s.write(escape(%s))\n' % (prefix, varname, replace)
  85.  
  86. # Evaluates key with data
  87. def genRepeatEval(varname, tid, prefix, replace):
  88.     return u'%s%s.write(%s)\n' % (prefix, varname, replace)
  89.  
  90. # Returns include iff function does not evaluate to true
  91. def genRepeatIncludeHide(varname, tid, prefix, args):
  92.     (ifValue, name) = args
  93.     f = open(resource.path('templates/%s'%name),'r')
  94.     text = f.read()
  95.     f.close()
  96.     out = u'%sif not (%s):\n'%(prefix, ifValue)
  97.     out = u'%s%s    %s.write(%s)\n' % (out, prefix, varname, repr(text.decode('utf-8')))
  98.     return out
  99.  
  100. def genHideSection(varname, tid, prefix, args):
  101.     (ifValue, funcList) = args
  102.     out = u'%sif not (%s):\n'%(prefix, ifValue)
  103.     for (func, newargs) in funcList:
  104.         val = func(varname,tid,prefix+'    ',newargs)
  105.         checkU(val)
  106.         out = u'%s%s' % (out, val)
  107.     return out
  108.  
  109. def genQuoteAndFillAttr(varname, tid, prefix, value):
  110.     checkU(value)
  111.     return u'%s%s.write(quoteAndFillAttr(%s,locals()))\n'%(prefix,varname,repr(value))
  112.     
  113. def genUpdateHideOnView(varname, tid, prefix, args):
  114.     (viewName, ifValue, attrs, nodeId) = args
  115.  
  116.     out = u'%s_hideFunc = lambda : %s\n' % (prefix, ifValue)
  117.     out = u'%s%s_dynHide = _hideFunc()\n' % (out, prefix)
  118.     out = u'%s%sif _dynHide:\n' % (out, prefix)
  119.     out = u'%s%s    %s.write(u" style=\\\"display:none\\\">")\n' % (
  120.         out, prefix, varname)
  121.     out = u'%s%selse:\n%s    %s.write(u">")\n' % (
  122.         out, prefix, prefix, varname)
  123.  
  124.     out = u'%s%shandle.addUpdateHideOnView(%s,%s,_hideFunc,_dynHide)\n' % (
  125.         out, prefix, repr(nodeId), viewName)
  126.     return out
  127.  
  128. def genInsertBodyTagExtra(varname, tid, prefix, args):
  129.     return u'%s%s.write(u" " + bodyTagExtra)\n' % (prefix, varname)
  130.  
  131. def genExecuteTemplate(varname, tid, prefix, args):
  132.     filename, methodArgs = args
  133.     methodCall = u"fillStaticTemplate(%r, onlyBody=True" % filename
  134.     for name, value in methodArgs.items():
  135.         methodCall += u', %s=%s' % (name, value)
  136.     methodCall += u')'
  137.     return u'%s%s.write(%s)\n' % (prefix, varname, methodCall)
  138.  
  139. from xml import sax
  140. from StringIO import StringIO # Oh! Why can't cStringIO support unicode?
  141. from templatehelper import quoteattr, escape, HTMLPattern, attrPattern, resourcePattern, rawAttrPattern, generateId
  142. import re
  143. import os
  144. import stat
  145.  
  146. #Setup gettext
  147. #gettext.install('dtv', 'resources/gettext')
  148.  
  149. # Limitations:
  150. # - t:hideIf tags are only dynamically updated whenever there is dynamic
  151. #   activity on a t:repeatForView in the document (easily fixed)
  152. # - Style tags are overwritten for hidden nodes
  153. # - id tags are inserted in appropriate places
  154. # - An empty span is always created after a repeatForView, so we can't
  155. #   use it inside of a table
  156. # - Currently, we're using the translate and name attributes found on
  157. #   this page to mark text for translation
  158. #   http://www.zope.org/DevHome/Wikis/DevSite/Projects/ComponentArchitecture/ZPTInternationalizationSupport
  159.  
  160. # To Do:
  161. # - Take a lock around the entire database while template is being filled
  162. # - Improve error handling (currently some random exception is thrown if
  163. #   the template is poorly structured)
  164. #
  165. #    Eventually, we may add support for more attributes
  166.  
  167. class res:
  168.     def path(self, rel_path):
  169.         global resourcePath
  170.         return os.path.join(resourcePath, rel_path)
  171.  
  172. resource = res()
  173.  
  174. def setResourcePath(path):
  175.     global resourcePath
  176.     resourcePath = path
  177.  
  178. ###############################################################################
  179. #### Public interface                                                      ####
  180. ###############################################################################
  181.  
  182. #
  183. # Compile the template given in inFile to python code in outFile
  184. #
  185. def compileTemplate(inFile, *args, **kwargs):
  186.     handle = MetaHandle()
  187.     tcc = TemplateContentCompiler(handle, inFile, *args, **kwargs)
  188.     p = sax.make_parser()
  189.     p.setFeature(sax.handler.feature_external_ges, False)
  190.     p.setContentHandler(tcc)
  191.     p.parse(resource.path("templates/%s" % inFile))
  192.     return (tcc, handle)      
  193.  
  194. # Returns a list of templates with URLs relative to the template
  195. # resource directory
  196. def findTemplates(tpath):
  197.     templates = []
  198.     folders = []
  199.     for template in os.listdir(tpath):
  200.         if (template.find('.svn') == -1 and
  201.             template.find('_svn') == -1 and
  202.             not (template.startswith('.') or template.startswith('#') or 
  203.                  template.endswith('~') or template.endswith('.js') or 
  204.                  template.endswith('.html'))):
  205.             mode = os.stat(os.path.join(tpath,template))[stat.ST_MODE]
  206.             if stat.S_ISDIR(mode):
  207.                 folders.append(template)
  208.             else:
  209.                 templates.append(template)        
  210.     return (folders, templates)
  211.  
  212. def modifiedTime(dir):
  213.     maxTime = 0
  214.     try:
  215.         for (dirpath, dirnames, filenames) in os.walk(dir):
  216.             for f in filenames:
  217.                 if -1 == dirpath.find('.svn') and -1 == dirpath.find('_svn'):
  218.                     t = os.stat(os.path.join(dirpath, f)).st_mtime
  219.                     if t > maxTime:
  220.                         maxTime = t
  221.         return maxTime
  222.     except:
  223.         return 0
  224.  
  225. def compileAllTemplates(root):
  226.     setResourcePath(os.path.join(root,'resources'))
  227.     source = resource.path('templates')
  228.     sourceTime = modifiedTime(source)
  229.     dest = resource.path(os.path.join('..','portable','compiled_templates'))
  230.     if not os.path.isdir(dest):
  231.         os.makedirs(dest)
  232.     destTime = modifiedTime(dest)
  233.     compilerTime = os.stat(resource.path(os.path.join(
  234.                             '..','portable','template_compiler.py'))).st_mtime
  235.     if (sourceTime > destTime) or (compilerTime > destTime):
  236.         compileTemplates()
  237.  
  238. def compileTemplates(tpath = None):
  239.     outdir = resource.path(os.path.join('..','portable','compiled_templates'))
  240.     if not os.path.isdir(outdir):
  241.         os.makedirs(outdir)
  242.     indir = resource.path('templates')
  243.     if tpath is not None:
  244.         print "Compiling %s" % tpath
  245.         outdir = os.path.join(outdir, tpath)
  246.         try:
  247.             os.makedirs(outdir)
  248.         except:
  249.             pass
  250.         indir = os.path.join(indir, tpath)
  251.     
  252.     manifest = open(os.path.join(outdir,'__init__.py'),'wb')
  253.     manifest.write('# This is a generated file. Do not edit.\n\n')
  254.  
  255.     (folders, templates) = findTemplates(indir)
  256.     for template in templates:
  257.         outFile = os.path.join(outdir,template.replace('-','_')+'.py')
  258.         if tpath is None:
  259.             sourceFile = template
  260.         else:
  261.             sourceFile = os.path.join(tpath, template)
  262.         print "Compiling '%s' template to %s" % (sourceFile, outFile)
  263.         (tcc, handle) = compileTemplate(sourceFile)
  264.         f = open(outFile,"wb")
  265.         f.write(tcc.getOutput().encode('utf-8'))
  266.         f.close()
  267.         manifest.write("import %s\n" % template.replace('/','.').replace('\\','.').replace('-','_'))
  268.     for folder in folders:
  269.         manifest.write("import %s\n" % folder.replace('/','.').replace('\\','.').replace('-','_'))
  270.         compileTemplates(folder)
  271.     manifest.close()
  272.  
  273. class TemplateError(Exception):
  274.     def __init__(self, message):
  275.         self.message = message
  276.     def __str__(self):
  277.         return self.message
  278.  
  279. ###############################################################################
  280. #### Main template filling code                                            ####
  281. ###############################################################################
  282.  
  283. ##
  284. # SAX version of templating code
  285. class TemplateContentCompiler(sax.handler.ContentHandler):
  286.     def __init__(self, handle, name, debug = False, onlyBody = False,
  287.             addIdToTopLevelElement=False):
  288.         self.handle = handle
  289.         self.debug = debug
  290.         self.onlyBody = onlyBody
  291.         self.addIdToTopLevelElement = addIdToTopLevelElement
  292.         self.name = name
  293.         self.isDynamic = False
  294.  
  295.     def getOperationList(self):
  296.         return self.outputLists[0]
  297.         
  298.     def getOutput(self, data = None):
  299.         fo = StringIO()
  300.         self.render(fo)
  301.         return fo.getvalue()
  302.  
  303.     def render(self, fileobj):
  304.         fileobj.write(u'# This is a generated file. Do not edit.\n')
  305.         fileobj.write(u'from template import Handle, fillAttr, quoteAndFillAttr, fillStaticTemplate\n')
  306.         fileobj.write(u'from StringIO import StringIO\n')
  307.         fileobj.write(u'from xhtmltools import urlencode\n')
  308.         fileobj.write(u'from templatehelper import quoteattr, escape\n')
  309.         fileobj.write(u'from string import Template\n')
  310.         fileobj.write(u'import app\n')
  311.         fileobj.write(u'import views\n')
  312.         fileobj.write(u'import sorts\n')
  313.         fileobj.write(u'import indexes\n')
  314.         fileobj.write(u'import filters\n')
  315.         fileobj.write(u'import resources\n')
  316.         fileobj.write(u'import gtcache\n')
  317.         fileobj.write(u'_ = gtcache.gettext\n')
  318.         fileobj.write(u'def fillTemplate(domHandler, dtvPlatform, eventCookie, bodyTagExtra, *args, **kargs):\n')
  319.         self.handle.render(fileobj)
  320.         fileobj.write(u'\n\n    out = StringIO()\n')
  321.         
  322.         if not self.onlyBody:
  323.             fileobj.write(u'    out.write(u"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?>\\n<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Strict//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\\\">\\n")\n')
  324.             
  325.         for count in range(len(self.outputLists[0])):
  326.             (func, args) = self.outputLists[0][count]
  327.             fileobj.write(func(u'out',u'',u'    ',args))
  328.  
  329.         fileobj.write(u'    out.seek(0)\n')
  330.         fileobj.write(u'\n\n    return (out, handle)\n')
  331.  
  332.     def returnIf(self,bool,value):
  333.         if bool:
  334.             return value
  335.         else:
  336.             return ''
  337.  
  338.     def startDocument(self):
  339.         print "Starting compile of %s" % self.name
  340.         self.elementStack = []
  341.         self.inInclude = False
  342.         self.inRepeatView = False
  343.         self.inRepeatWithTemplate = False
  344.         self.inUpdateView = False
  345.         self.inConfigUpdate = False
  346.         self.inReplace = False
  347.         self.inStaticReplace = False
  348.         self.inExecOnUnload = False
  349.         self.inExecOnLoad = False
  350.         self.repeatDepth = 0
  351.         self.hotspotDepth = -1
  352.         self.hotspotTagName = ''
  353.         self.replaceDepth = 0
  354.         self.hiding = False
  355.         self.hideDepth = []
  356.         self.depth = 0
  357.         self.bodyDepth = None
  358.         self.repeatName = ''
  359.         self.started = not self.onlyBody
  360.         self.outputLists = [[]]
  361.         self.outputText = []
  362.         self.hidingParams = []
  363.         self.translateDepth = []
  364.         self.translateText = []
  365.         self.translateDict = []
  366.         self.translateName = []
  367.         self.inRaw = False
  368.         self.rawDepth = None
  369.         
  370.  
  371.     def endDocument(self):
  372.         print "Ending compile"
  373.         self.endText()
  374.  
  375.     def startElement(self,name, attrs):
  376.         self.depth = self.depth + 1
  377.         if name == 'body':
  378.             self.bodyDepth = self.depth
  379.  
  380.         if (len(self.translateDepth) > 0 and
  381.                                    self.depth == self.translateDepth[-1]+1):
  382.             if not attrs.has_key('i18n:name'):
  383.                 print "in raw for %s" % name
  384.                 self.inRaw = True
  385.                 self.rawDepth = self.depth
  386.             else:
  387.                 self.translateName[-1] = attrs['i18n:name']
  388.                 self.translateText[-1] += u'${%s}' % self.translateName[-1]
  389.                 self.endText()
  390.                 self.outputLists.append([])
  391.  
  392.         if self.inRaw:
  393.             self.translateText[-1] += u"<%s" % name
  394.             for attr in attrs.keys():
  395.                 self.translateText[-1] += u' %s="%s"' % (attr, quoteattr(attrs[attr]))
  396.             self.translateText[-1] += u'>'
  397.         elif self.onlyBody and not self.started:
  398.             if name == 'body':
  399.                 self.started = True
  400.         elif not self.started:
  401.             pass
  402.         elif 't:repeatForView' in attrs.keys():
  403.             if 't:containerDiv' in attrs.keys():
  404.                 self.inRepeatWithContainerDiv = True
  405.                 self.repeatContainerId = generateId()
  406.                 self.addElementStart('div', {'id': self.repeatContainerId})
  407.             else:
  408.                 self.inRepeatWithContainerDiv = False
  409.             self.startRepeat(attrs['t:repeatForView'])
  410.             if not 't:repeatTemplate' in attrs.keys():
  411.                 self.addElementStart(name, attrs, addId=True)
  412.                 self.inRepeatWithTemplate = False
  413.             else:
  414.                 name = attrs['t:repeatTemplate']
  415.                 self.addFillTemplate(name, addIdToTopLevelElement=True)
  416.                 # if t:repeatTemplate is set, there shouldn't be any children
  417.                 # of this element.  Create a new outputList to check this.
  418.                 self.outputLists.append([]) 
  419.                 self.inRepeatWithTemplate = True
  420.         elif 't:updateForView' in attrs.keys():
  421.             self.startUpdate(attrs['t:updateForView'])
  422.             self.addElementStart(name, attrs, addId=True)
  423.         elif 't:updateForConfigChange' in attrs.keys():
  424.             self.startConfigUpdate()
  425.             self.addElementStart(name, attrs, addId=True)
  426.         elif 't:hideIf' in attrs.keys():
  427.             ifValue = attrs['t:hideIf']
  428.             if attrs.has_key('t:updateHideOnView'):
  429.                 if self.inRepeatView or self.inUpdateView:
  430.                     print "Warning: t:updateHideOnView is unsupported inside a repeat view"
  431.                 self.addUpdateHideOnView(attrs['t:updateHideOnView'],name, ifValue, attrs)
  432.             else:
  433.                 self.startHiding(ifValue)
  434.                 self.addElementStart(name, attrs)
  435.                 #FIXME: support i18n tags within a t:hideIf
  436.         elif 't:showIf' in attrs.keys():
  437.             ifValue = u"not (%s)" % attrs['t:showIf']
  438.             if attrs.has_key('t:updateHideOnView'):
  439.                 if self.inRepeatView or self.inUpdateView:
  440.                     print "Warning: t:updateHideOnView is unsupported inside a repeat view"
  441.                 self.addUpdateHideOnView(attrs['t:updateHideOnView'],name, ifValue, attrs)
  442.             else:
  443.                 self.startHiding(ifValue)
  444.                 self.addElementStart(name, attrs)
  445.                 #FIXME: support i18n tags within a t:showIf
  446.         elif 'i18n:translate' in attrs.keys():
  447.             self.addElementStart(name, attrs)
  448.             self.translateDepth.append(self.depth)
  449.             self.translateText.append('')
  450.             self.translateDict.append({})
  451.             self.translateName.append('')
  452.             
  453.         elif name == 't:include':
  454.             self.addInclude(attrs['filename'])
  455.  
  456.         elif 't:replace' in attrs.keys():
  457.                 self.addElementStart(name, attrs)
  458.                 replace = attrs['t:replace']
  459.                 self.addEvalEscape(replace)
  460.                 self.inReplace = True
  461.                 self.replaceDepth = self.depth      
  462.         elif 't:replaceMarkup' in attrs.keys():
  463.                 self.addElementStart(name, attrs)
  464.                 replace = attrs['t:replaceMarkup']
  465.                 self.addEval(replace)
  466.                 self.inReplace = True
  467.                 self.replaceDepth = self.depth      
  468.         elif name == 't:execOnUnload':
  469.             self.inExecOnUnload = True
  470.             self.code = ''
  471.         elif name == 't:execOnLoad':
  472.             self.inExecOnLoad = True
  473.             self.code = ''
  474.         elif name == 't:include':
  475.             f = open(resource.path('templates/%s'%attrs['filename']),'r')
  476.             html = f.read()
  477.             f.close()
  478.             self.addText(html.decode('utf-8'))
  479.         elif name == 't:staticReplaceMarkup':
  480.             replace = attrs['t:replaceData']
  481.             self.addEval(replace)
  482.             self.inStaticReplace = True
  483.         elif name == 't:staticReplace':
  484.             replace = attrs['t:replaceData']
  485.             self.addEvalEscape(replace)
  486.             self.inStaticReplace = True
  487.         elif name == 't:includeTemplate':
  488.             self.addFillTemplate(attrs['filename'])
  489.         elif name == 't:executeTemplate':
  490.             args = dict(attrs.items())
  491.             filename = args.pop('filename')
  492.             self.addInstruction(genExecuteTemplate, (filename, args))
  493.         elif name == 't:triggerActionOnLoad':
  494.             self.handle.addTriggerActionURLOnLoad(attrs['url'])
  495.         elif name == 't:triggerActionOnUnload':
  496.             self.handle.addTriggerActionURLOnUnload(attrs['url'])
  497.         elif 't:hotspot' in attrs.keys():
  498.             self.addHotspot(name, attrs['t:hotspot'])
  499.         else:
  500.             self.addElementStart(name, attrs)
  501.  
  502.     def endElement(self,name):
  503.         if not self.started:
  504.             pass
  505.         elif self.inRaw:
  506.             self.translateText[-1] += u'</%s>' % name
  507.         elif self.onlyBody and name == 'body':
  508.             self.started = False
  509.         elif name == 't:include':
  510.             pass
  511.         elif name == 't:staticReplace':
  512.             self.inStaticReplace = False
  513.         elif name == 't:staticReplaceMarkup':
  514.             self.inStaticReplace = False
  515.         elif name == 't:filter':
  516.             pass
  517.         elif name == 't:sort':
  518.             pass
  519.         elif name == 't:includeTemplate':
  520.             pass
  521.         elif name == 't:executeTemplate':
  522.             pass
  523.         elif name == 't:triggerActionOnUnload':
  524.             pass
  525.         elif name == 't:triggerActionOnLoad':
  526.             pass
  527.         elif self.hiding and self.depth == self.hideDepth[-1]:
  528.             self.addText(u'</%s>'%name)
  529.             self.endHiding()
  530.         elif self.inReplace and self.depth == self.replaceDepth:
  531.             self.addText(u'</%s>'%name)
  532.             self.inReplace = False
  533.         elif self.inRepeatView and self.depth == self.repeatDepth:
  534.             if self.inRepeatWithTemplate:
  535.                 self.endText()
  536.                 nestedOutputs = self.outputLists.pop()
  537.                 if nestedOutputs:
  538.                     m = "Elements with t:repeatTemplate, can't have children"
  539.                     raise ValueError(m)
  540.             else:
  541.                 self.addText(u'</%s>'%name)
  542.                 self.endText()
  543.             repeatList = self.endRepeat()
  544.             if self.inRepeatWithContainerDiv:
  545.                 self.endElement('div')
  546.                 self.handle.addView(self.repeatContainerId, 'containerDiv', repeatList, self.repeatName)
  547.             else:
  548.                 repeatId = generateId()
  549.                 self.addText(u'<span id="%s"/>'%quoteattr(repeatId))
  550.                 self.handle.addView(repeatId, 'nextSibling', repeatList, self.repeatName)
  551.         elif self.inUpdateView and self.depth == self.repeatDepth:
  552.             self.addText(u'</%s>'%name)
  553.             self.endText()
  554.             repeatList = self.endUpdate()
  555.             repeatId = generateId()
  556.             self.addText(u'<span id="%s"/>'%quoteattr(repeatId))
  557.             self.handle.addUpdate(repeatId, 'nextSibling', repeatList, self.repeatName)
  558.         elif self.inConfigUpdate and self.depth == self.repeatDepth:
  559.             self.addText(u'</%s>'%name)
  560.             self.endText()
  561.             repeatList = self.endConfigUpdate()
  562.             repeatId = generateId()
  563.             self.addText(u'<span id="%s"/>'%quoteattr(repeatId))
  564.             self.handle.addConfigUpdate(repeatId, 'nextSibling', repeatList)
  565.         elif (len(self.translateDepth) > 0 and
  566.                                      self.depth == self.translateDepth[-1]):
  567.             self.addTranslation()
  568.             self.addText(u'</%s>'%name)
  569.         elif self.depth == self.hotspotDepth:
  570.             self.addText(u"</%s><!-- HOT SPOT END -->" % self.hotspotTagName)
  571.             self.hotspotDepth = -1
  572.         elif name == 't:execOnUnload':
  573.             self.inExecOnUnload = False
  574.             self.handle.addExecOnUnload(self.code)
  575.         elif name == 't:execOnLoad':
  576.             self.inExecOnLoad = False
  577.             self.handle.addExecOnLoad(self.code)
  578.         else:
  579.             self.addText(u'</%s>'%name)
  580.         if (len(self.translateDepth) > 0 and
  581.                                    self.depth == self.translateDepth[-1]+1):
  582.             if self.inRaw:
  583.                 print "ending raw for %s" % name
  584.                 self.inRaw = False
  585.             else:
  586.                 self.endText()
  587.                 self.translateDict[-1][self.translateName[-1]] = self.outputLists.pop()
  588.         self.depth = self.depth - 1
  589.  
  590.     def characters(self,data):
  591.         if not self.started:
  592.             pass
  593.         elif self.inReplace or self.inStaticReplace:
  594.             pass
  595.         elif ((len(self.translateDepth) > 0 and
  596.                                       self.depth == self.translateDepth[-1]) or
  597.                 self.inRaw):
  598.               self.translateText[-1] += data
  599.         elif self.inExecOnUnload or self.inExecOnLoad:
  600.             self.code += data
  601.         else:
  602.             self.addTextEscape(data)
  603.  
  604.     def skippedEntity(self, name):
  605.         self.addText(u"&%s;" % name)
  606.  
  607.     def addTranslation(self):
  608.         self.translateDepth.pop()
  609.         self.translateName.pop()
  610.         self.addInstruction(genRepeatTranslate, (self.translateText.pop(), self.translateDict.pop()))
  611.  
  612.     def addInclude(self, template):
  613.         f = open(resource.path('templates/%s'%template),'r')
  614.         html = f.read()
  615.         f.close()
  616.         self.addText(html)
  617.  
  618.     def addFillTemplate(self, name, *args, **kwargs):
  619.         print "  compiling '%s' subtemplate" % name
  620.         (tcc, handle) = compileTemplate(name, onlyBody=True, *args, **kwargs)
  621.         if tcc.isDynamic:
  622.             self.isDynamic = True
  623.             if self.inUpdateView or self.inConfigUpdate or self.inRepeatView:
  624.                 raise TemplateError, "Nested Dynamic tags"
  625.         self.handle.addSubHandle(handle)
  626.         self.addInstructions(tcc.getOperationList())
  627.  
  628.     def addIdAndClose(self):
  629.         self.addText(' id="')
  630.         self.addInstruction(genRepeatTID,None)
  631.         self.addText('">')
  632.  
  633.     def startRepeat(self, name):
  634.         if self.inUpdateView or self.inConfigUpdate or self.inRepeatView:
  635.             raise TemplateError, "Nested Dynamic tags"
  636.         self.endText()
  637.         self.inRepeatView = True
  638.         self.isDynamic = True
  639.         self.repeatDepth = self.depth
  640.         self.repeatName = name
  641.         self.outputLists.append([])
  642.  
  643.     def startUpdate(self, name):
  644.         if self.inUpdateView or self.inConfigUpdate or self.inRepeatView:
  645.             raise TemplateError, "Nested Dynamic tags"
  646.         self.endText()
  647.         self.inUpdateView = True
  648.         self.isDynamic = True
  649.         self.repeatDepth = self.depth
  650.         self.repeatName = name
  651.         self.outputLists.append([])
  652.  
  653.     def startConfigUpdate(self):
  654.         if self.inUpdateView or self.inConfigUpdate or self.inRepeatView:
  655.             raise TemplateError, "Nested Dynamic tags"
  656.         self.endText()
  657.         self.inConfigUpdate = True
  658.         self.isDynamic = True
  659.         self.repeatDepth = self.depth
  660.         self.outputLists.append([])
  661.  
  662.     def endRepeat(self):
  663.         self.inRepeatView = False
  664.         return self.outputLists.pop()
  665.  
  666.     def endUpdate(self):
  667.         self.inUpdateView = False
  668.         return self.outputLists.pop()
  669.  
  670.     def endConfigUpdate(self):
  671.         self.inConfigUpdate = False
  672.         return self.outputLists.pop()
  673.  
  674.     def startHiding(self,ifValue):
  675.         self.endText()
  676.         self.hidingParams.append(ifValue)
  677.         self.outputLists.append([])
  678.         self.hideDepth.append(self.depth)
  679.         self.hiding = True
  680.  
  681.     def endHiding(self):
  682.         self.endText()
  683.         ifValue = self.hidingParams.pop()
  684.         funcList = self.outputLists.pop()
  685.         self.hideDepth.pop()
  686.         self.hiding = len(self.hideDepth) > 0
  687.         self.addInstruction(genHideSection, (ifValue, funcList))
  688.  
  689.     def addText(self, text):
  690.         self.outputText.append( text)
  691.  
  692.     def addElementStart(self, name, attrs, addId=False):
  693.         if (self.bodyDepth is not None and self.depth == self.bodyDepth + 1
  694.                 and self.addIdToTopLevelElement):
  695.             addId = True
  696.         self.addText(u'<%s'%name)
  697.         for key in attrs.keys():
  698.             if (not (key.startswith('t:') or key.startswith('i18n:')) or
  699.                     key == 't:contextMenu'):
  700.                 self.addAttr(key,attrs[key])
  701.         if name.lower() == 'body':
  702.             self.addInstruction(genInsertBodyTagExtra, None)
  703.         if addId:
  704.             self.addIdAndClose()
  705.         else:
  706.             self.addText(u'>')
  707.  
  708.     def addTextEscape(self, text):
  709.         self.outputText.append( escape(text))
  710.  
  711.     def addUpdateHideOnView(self, viewName, name, ifValue, attrs):
  712.         nodeId = generateId()
  713.         self.addText(u"<%s" % name)
  714.         for key in attrs.keys():
  715.             if not key in ['t:hideIf','t:updateHideOnView','style']:
  716.                 self.addText(u" %s=" % key)
  717.                 self.addInstruction(genQuoteAndFillAttr, attrs[key])
  718.         self.addText(u' id="%s"' % quoteattr(nodeId))
  719.         self.addInstruction(genUpdateHideOnView,(viewName, ifValue, attrs, nodeId))
  720.  
  721.     def addAttr(self, attr, value):
  722.         self.addText(u' %s="' % attr)
  723.         self.addDynamicText(value)
  724.         self.addText('"')
  725.  
  726.     def addDynamicText(self, value):
  727.         match = attrPattern.match(value)
  728.         if match:
  729.             while match:
  730.                 self.addText(quoteattr(match.group(1)))
  731.                 self.addInstruction(genQuoteAttr,match.group(2))
  732.                 value = match.group(3)
  733.                 match = attrPattern.match(value)
  734.             self.addText(u'%s' % quoteattr(value))
  735.         else:
  736.             match = rawAttrPattern.match(value)
  737.             if match:
  738.                 while match:
  739.                     self.addText(quoteattr(match.group(1)))
  740.                     self.addInstruction(genRawAttr,match.group(2))
  741.                     value = match.group(3)
  742.                     match = rawAttrPattern.match(value)
  743.                 self.addText(u'%s' % quoteattr(value))
  744.             else:
  745.                 match = resourcePattern.match(value)
  746.                 if match:
  747.                     self.addInstruction(genRawAttr,u'resources.url(%s)'%repr(match.group(1)))
  748.                 else:
  749.                     self.addText(quoteattr(value))
  750.  
  751.     def addHotspot(self, name, id):
  752.         self.hotspotDepth = self.depth
  753.         self.hotspotTagName = name
  754.         self.addText(u"<!-- HOT SPOT ")
  755.         self.addDynamicText(id)
  756.         self.addText(u" -->")
  757.         self.addElementStart(name, {'id': id})
  758.  
  759.     def addEval(self,replace):
  760.         self.addInstruction(genRepeatEval,replace)
  761.  
  762.     def addEvalEscape(self,replace):
  763.         self.addInstruction(genRepeatEvalEscape, replace)
  764.  
  765.     def endText(self):
  766.         if len(self.outputText) > 0:
  767.             self.addInstruction(genRepeatText,u''.join(self.outputText))
  768.         self.outputText = []
  769.  
  770.     def addInstruction(self, instruction, args):
  771.         if instruction != genRepeatText:
  772.             self.endText()
  773.         self.outputLists[-1].append((instruction,args))
  774.         
  775.     def addInstructions(self, instructions):
  776.         self.endText()
  777.         for (ins, arg) in instructions:
  778.             self.addInstruction(ins, arg)
  779.  
  780. ###############################################################################
  781. #### Generating Javascript callbacks to keep document updated              ####
  782. ###############################################################################
  783.  
  784. # Object representing data needed to register Javascript callbacks
  785. #
  786. # This is used by TemplateContentCompiler to generate code to create a
  787. # Handle
  788. class MetaHandle:
  789.     def __init__(self):
  790.         self.trackedViews = []
  791.         self.updateRegions = []
  792.         self.configUpdateRegions = []
  793.         self.subHandles = []
  794.         self.triggerActionURLsOnLoad = []
  795.         self.triggerActionURLsOnUnload = []
  796.         self.execOnUnload = None
  797.         self.execOnLoad = None
  798.         
  799.     def addTriggerActionURLOnLoad(self,url):
  800.         self.triggerActionURLsOnLoad.append(str(url))
  801.  
  802.     def addTriggerActionURLOnUnload(self, url):
  803.         self.triggerActionURLsOnUnload.append(str(url))
  804.  
  805.     def getTriggerActionURLsOnLoad(self):
  806.         return self.triggerActionURLsOnLoad
  807.  
  808.     def getTriggerActionURLsOnUnload(self):
  809.         return self.triggerActionURLsOnUnload
  810.  
  811.     def addExecOnUnload(self, code):
  812.         self.execOnUnload = code
  813.  
  814.     def addExecOnLoad(self, code):
  815.         self.execOnLoad = code
  816.  
  817.     def addView(self, anchorId, anchorType, templateFuncs, name):
  818.         # Register for JS calls to populate a t:repeatFor. 'view' is the
  819.         # database view to track; 'node' is a DOM node representing the
  820.         # template to fill; 'data' are extra variables to be used in expanding
  821.         # the template. The 'anchor*' arguments tell where in the document
  822.         # to place the expanded template nodes. If 'anchorType' is
  823.         # 'nextSibling', 'anchorId' is the id attribute of the tag immediately
  824.         # following the place the template should be expanded. If it is
  825.         # 'parentNode', the template should be expanded so as to be the final
  826.         # child in the node whose id attribute matches 'anchorId'.
  827.         # 'containerDiv' is like parentNode, except it's contained in an
  828.         # auto-generated <div> element.  This allows for efficient changes
  829.         # when the view is re-sorted.
  830.         #
  831.         # We take a private copy of 'node', so don't worry about modifying
  832.         # it subsequent to calling this method.
  833.         tv = (anchorId, anchorType, templateFuncs, name)
  834.         self.trackedViews.append(tv)
  835.  
  836.     def addUpdate(self, anchorId, anchorType, templateFuncs, name):
  837.         ur = (anchorId, anchorType, templateFuncs, name)
  838.         self.updateRegions.append(ur)
  839.  
  840.     def addConfigUpdate(self, anchorId, anchorType, templateFuncs):
  841.         ur = (anchorId, anchorType, templateFuncs)
  842.         self.configUpdateRegions.append(ur)
  843.     
  844.     def addSubHandle(self, handle):
  845.         self.subHandles.append(handle)
  846.  
  847.     def render(self, fileobj, varname = 'handle'):
  848.         prefix = u'    '
  849.         ending = u"\n"
  850.         
  851.         fileobj.write(u'%s# Start of handle%s%s' % (prefix, ending, ending))
  852.  
  853.         fileobj.write(u'%s# Start user code%s' % (prefix, ending))
  854.         if self.execOnLoad is not None:
  855.             for line in self.execOnLoad.splitlines():
  856.                 fileobj.write(u'%s%s%s' % (prefix, line, ending))
  857.         if self.execOnUnload is not None:
  858.             fileobj.write(u'%s%sdef _execOnUnload():%s' % (ending, prefix, ending))
  859.             for line in self.execOnUnload.splitlines():
  860.                 fileobj.write(u'%s    %s%s' % (prefix, line, ending))
  861.         fileobj.write(u'%s# End user code%s%s' % (prefix, ending, ending))
  862.  
  863.         fileobj.write(u'%slocalvars = locals()%s' % (prefix, ending))
  864.         fileobj.write(u'%slocalvars.update(globals())%s' % (prefix, ending))
  865.         if self.execOnUnload is not None:
  866.             fileobj.write(u'%s%s = Handle(domHandler, localvars, onUnlink = _execOnUnload)%s%s' % (prefix, varname, ending, ending))
  867.         else:
  868.             fileobj.write(u'%s%s = Handle(domHandler, localvars, onUnlink = lambda:None)%s%s' % (prefix, varname, ending, ending))
  869.  
  870.         count = 0
  871.         for ur in self.updateRegions:
  872.             (anchorId, anchorType, templateFuncs, name) = ur
  873.             upFunc = u"up_%s_%s" % (count, varname)
  874.             fileobj.write(u'%sdef %s(viewName, view, tid):%s' % (prefix, upFunc,ending))
  875.             fileobj.write(u'%s    out = StringIO()%s' % (prefix, ending))
  876.             for count2 in range(len(templateFuncs)):
  877.                 (func, args) = templateFuncs[count2]
  878.                 fileobj.write(func(u'out',u'',prefix+u'    ',args))
  879.             fileobj.write(u'%s    out.seek(0)%s' % (prefix, ending))
  880.             fileobj.write(u'%s    return out%s' % (prefix, ending))
  881.  
  882.             fileobj.write(u'%s%s.addUpdate(%s,%s,%s,%s, %s)%s' % (prefix, varname, repr(anchorId),repr(anchorType),name,upFunc,repr(name),ending))
  883.             count += 1
  884.  
  885.         for ur in self.configUpdateRegions:
  886.             (anchorId, anchorType, templateFuncs) = ur
  887.             upFunc = u"config_up_%s_%s" % (count, varname)
  888.             fileobj.write(u'%sdef %s(tid):%s' % (prefix, upFunc, ending))
  889.             fileobj.write(u'%s    out = StringIO()%s' % (prefix, ending))
  890.             for count2 in range(len(templateFuncs)):
  891.                 (func, args) = templateFuncs[count2]
  892.                 fileobj.write(func('out','',prefix+'    ',args))
  893.             fileobj.write(u'%s    out.seek(0)%s' % (prefix, ending))
  894.             fileobj.write(u'%s    return out%s' % (prefix, ending))
  895.  
  896.             fileobj.write(u'%s%s.addConfigUpdate(%s,%s,%s)%s' % (prefix, varname, repr(anchorId),repr(anchorType),upFunc,ending))
  897.             count += 1
  898.  
  899.         for tv in self.trackedViews:
  900.             (anchorId, anchorType, templateFuncs, name) = tv
  901.             repFunc = u"rep_%s_%s" % (count, varname)
  902.             fileobj.write(u'%sdef %s(this, viewName, view, tid):%s' % (prefix, repFunc,ending))
  903.             fileobj.write(u'%s    out = StringIO()%s' % (prefix, ending))
  904.             for count2 in range(len(templateFuncs)):
  905.                 (func, args) = templateFuncs[count2]
  906.                 fileobj.write(func('out','',prefix+'    ',args))
  907.             fileobj.write(u'%s    out.seek(0)%s' % (prefix, ending))
  908.             fileobj.write(u'%s    return out%s' % (prefix, ending))
  909.  
  910.             fileobj.write(u'%s%s.addView(%s,%s,%s,%s, %s)%s' % (prefix, varname, repr(anchorId),repr(anchorType),name,repFunc,repr(name),ending))
  911.             count += 1
  912.             
  913.         for action in self.triggerActionURLsOnLoad:
  914.             fileobj.write(u'%s%s.addTriggerActionURLOnLoad(fillAttr(%s,locals()))%s' %
  915.                           (prefix, varname, repr(action), ending))
  916.  
  917.         for action in self.triggerActionURLsOnUnload:
  918.             fileobj.write(u'%s%s.addTriggerActionURLOnUnload(fillAttr(%s,locals()))%s' %
  919.                           (prefix, varname, repr(action), ending))
  920.  
  921.         for subHandle in range(len(self.subHandles)):
  922.             newVarName = u'%s_%d' % (varname, subHandle)
  923.             self.subHandles[subHandle].render(fileobj, newVarName)
  924.             fileobj.write(u'%s%s.addSubHandle(%s)%s'%
  925.                           (prefix,varname,newVarName,ending))
  926.             
  927.  
  928. def fillIfNotNone(obj):
  929.     if obj is None:
  930.         return repr(None)
  931.     else:
  932.         return u"fillAttr(%s,locals())" % repr(obj)
  933.